// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <nebula/types/fundamental.h>

namespace nebula {

    /// \brief Base type for all date and time types
    class TURBO_EXPORT TemporalType : public FixedWidthType {
    public:
        using FixedWidthType::FixedWidthType;

        // This is only for preventing defining this class in each
        // translation unit to avoid one-definition-rule violation.
        ~TemporalType() override = default;

        DataTypeLayout layout() const override {
            return DataTypeLayout(
                    {DataTypeLayout::Bitmap(), DataTypeLayout::FixedWidth(bit_width() / 8)});
        }
    };

    /// \brief Base type class for date data
    class TURBO_EXPORT DateType : public TemporalType {
    public:
        virtual DateUnit unit() const = 0;

    protected:
        explicit DateType(Type::type type_id);
    };

    /// Concrete type class for 32-bit date data (as number of days since UNIX epoch)
    class TURBO_EXPORT Date32Type : public DateType {
    public:
        static constexpr Type::type type_id = Type::DATE32;
        static constexpr DateUnit UNIT = DateUnit::DAY;
        using c_type = int32_t;
        using PhysicalType = Int32Type;

        static constexpr const char *type_name() { return "date32"; }

        Date32Type();

        int bit_width() const override { return static_cast<int>(sizeof(c_type) * CHAR_BIT); }

        std::string to_string(bool show_metadata = false) const override;

        std::string name() const override { return "date32"; }

        DateUnit unit() const override { return UNIT; }

    protected:
        std::string compute_fingerprint() const override;
    };


    /// Concrete type class for 64-bit date data (as number of milliseconds since UNIX epoch)
    class TURBO_EXPORT Date64Type : public DateType {
    public:
        static constexpr Type::type type_id = Type::DATE64;
        static constexpr DateUnit UNIT = DateUnit::MILLI;
        using c_type = int64_t;
        using PhysicalType = Int64Type;

        static constexpr const char *type_name() { return "date64"; }

        Date64Type();

        int bit_width() const override { return static_cast<int>(sizeof(c_type) * CHAR_BIT); }

        std::string to_string(bool show_metadata = false) const override;

        std::string name() const override { return "date64"; }

        DateUnit unit() const override { return UNIT; }

    protected:
        std::string compute_fingerprint() const override;
    };


    TURBO_EXPORT
    std::ostream &operator<<(std::ostream &os, TimeUnit::type unit);

    /// Base type class for time data
    class TURBO_EXPORT TimeType : public TemporalType, public ParametricType {
    public:
        TimeUnit::type unit() const { return unit_; }

    protected:
        TimeType(Type::type type_id, TimeUnit::type unit);

        std::string compute_fingerprint() const override;

        TimeUnit::type unit_;
    };


    /// Concrete type class for 32-bit time data (as number of seconds or milliseconds
    /// since midnight)
    class TURBO_EXPORT Time32Type : public TimeType {
    public:
        static constexpr Type::type type_id = Type::TIME32;
        using c_type = int32_t;
        using PhysicalType = Int32Type;

        static constexpr const char *type_name() { return "time32"; }

        int bit_width() const override { return static_cast<int>(sizeof(c_type) * CHAR_BIT); }

        explicit Time32Type(TimeUnit::type unit = TimeUnit::MILLI);

        std::string to_string(bool show_metadata = false) const override;

        std::string name() const override { return "time32"; }
    };

    /// Concrete type class for 64-bit time data (as number of microseconds or nanoseconds
    /// since midnight)
    class TURBO_EXPORT Time64Type : public TimeType {
    public:
        static constexpr Type::type type_id = Type::TIME64;
        using c_type = int64_t;
        using PhysicalType = Int64Type;

        static constexpr const char *type_name() { return "time64"; }

        int bit_width() const override { return static_cast<int>(sizeof(c_type) * CHAR_BIT); }

        explicit Time64Type(TimeUnit::type unit = TimeUnit::NANO);

        std::string to_string(bool show_metadata = false) const override;

        std::string name() const override { return "time64"; }
    };

    /// \brief Concrete type class for datetime data (as number of seconds, milliseconds,
    /// microseconds or nanoseconds since UNIX epoch)
    ///
    /// If supplied, the timezone string should take either the form (i) "Area/Location",
    /// with values drawn from the names in the IANA Time Zone Database (such as
    /// "Europe/Zurich"); or (ii) "(+|-)HH:MM" indicating an absolute offset from GMT
    /// (such as "-08:00").  To indicate a native UTC timestamp, one of the strings "UTC",
    /// "Etc/UTC" or "+00:00" should be used.
    ///
    /// If any non-empty string is supplied as the timezone for a TimestampType, then the
    /// Nebula field containing that timestamp type (and by extension the column associated
    /// with such a field) is considered "timezone-aware".  The integer arrays that comprise
    /// a timezone-aware column must contain UTC normalized datetime values, regardless of
    /// the contents of their timezone string.  More precisely, (i) the producer of a
    /// timezone-aware column must populate its constituent arrays with valid UTC values
    /// (performing offset conversions from non-UTC values if necessary); and (ii) the
    /// consumer of a timezone-aware column may assume that the column's values are directly
    /// comparable (that is, with no offset adjustment required) to the values of any other
    /// timezone-aware column or to any other valid UTC datetime value (provided all values
    /// are expressed in the same units).
    ///
    /// If a TimestampType is constructed without a timezone (or, equivalently, if the
    /// timezone supplied is an empty string) then the resulting Nebula field (column) is
    /// considered "timezone-naive".  The producer of a timezone-naive column may populate
    /// its constituent integer arrays with datetime values from any timezone; the consumer
    /// of a timezone-naive column should make no assumptions about the interoperability or
    /// comparability of the values of such a column with those of any other timestamp
    /// column or datetime value.
    ///
    /// If a timezone-aware field contains a recognized timezone, its values may be
    /// localized to that locale upon display; the values of timezone-naive fields must
    /// always be displayed "as is", with no localization performed on them.
    class TURBO_EXPORT TimestampType : public TemporalType, public ParametricType {
    public:
        using Unit = TimeUnit;

        static constexpr Type::type type_id = Type::TIMESTAMP;
        using c_type = int64_t;
        using PhysicalType = Int64Type;

        static constexpr const char *type_name() { return "timestamp"; }

        int bit_width() const override { return static_cast<int>(sizeof(int64_t) * CHAR_BIT); }

        explicit TimestampType(TimeUnit::type unit = TimeUnit::MILLI)
                : TemporalType(Type::TIMESTAMP), unit_(unit) {}

        explicit TimestampType(TimeUnit::type unit, const std::string &timezone)
                : TemporalType(Type::TIMESTAMP), unit_(unit), timezone_(timezone) {}

        std::string to_string(bool show_metadata = false) const override;

        std::string name() const override { return "timestamp"; }

        TimeUnit::type unit() const { return unit_; }

        const std::string &timezone() const { return timezone_; }

    protected:
        std::string compute_fingerprint() const override;

    private:
        TimeUnit::type unit_;
        std::string timezone_;
    };

    template<typename Sink>
    inline void turbo_stringify(Sink &sink, const DataType &t) {
        sink.Append(t.to_string());
    }

    // Base class for the different kinds of calendar intervals.
    class TURBO_EXPORT IntervalType : public TemporalType, public ParametricType {
    public:
        enum type {
            MONTHS, DAY_TIME, MONTH_DAY_NANO
        };

        virtual type interval_type() const = 0;

    protected:
        explicit IntervalType(Type::type subtype) : TemporalType(subtype) {}

        std::string compute_fingerprint() const override;
    };

    /// \brief Represents a number of months.
    ///
    /// Type representing a number of months.  Corresponds to YearMonth type
    /// in Schema.fbs (years are defined as 12 months).
    class TURBO_EXPORT MonthIntervalType : public IntervalType {
    public:
        static constexpr Type::type type_id = Type::INTERVAL_MONTHS;
        using c_type = int32_t;
        using PhysicalType = Int32Type;

        static constexpr const char *type_name() { return "month_interval"; }

        IntervalType::type interval_type() const override { return IntervalType::MONTHS; }

        int bit_width() const override { return static_cast<int>(sizeof(c_type) * CHAR_BIT); }

        MonthIntervalType() : IntervalType(type_id) {}

        std::string to_string(bool TURBO_ARG_UNUSED(show_metadata) = false) const override {
            return name();
        }

        std::string name() const override { return "month_interval"; }
    };

    /// \brief Represents a number of days and milliseconds (fraction of day).
    class TURBO_EXPORT DayTimeIntervalType : public IntervalType {
    public:
        struct DayMilliseconds {
            int32_t days = 0;
            int32_t milliseconds = 0;

            constexpr DayMilliseconds() = default;

            constexpr DayMilliseconds(int32_t days, int32_t milliseconds)
                    : days(days), milliseconds(milliseconds) {}

            bool operator==(DayMilliseconds other) const {
                return this->days == other.days && this->milliseconds == other.milliseconds;
            }

            bool operator!=(DayMilliseconds other) const { return !(*this == other); }

            bool operator<(DayMilliseconds other) const {
                return this->days < other.days || this->milliseconds < other.milliseconds;
            }
        };

        using c_type = DayMilliseconds;
        using PhysicalType = DayTimeIntervalType;

        static_assert(sizeof(DayMilliseconds) == 8,
                      "DayMilliseconds struct assumed to be of size 8 bytes");
        static constexpr Type::type type_id = Type::INTERVAL_DAY_TIME;

        static constexpr const char *type_name() { return "day_time_interval"; }

        IntervalType::type interval_type() const override { return IntervalType::DAY_TIME; }

        DayTimeIntervalType() : IntervalType(type_id) {}

        int bit_width() const override { return static_cast<int>(sizeof(c_type) * CHAR_BIT); }

        std::string to_string(bool TURBO_ARG_UNUSED(show_metadata) = false) const override {
            return name();
        }

        std::string name() const override { return "day_time_interval"; }
    };

    TURBO_EXPORT
    std::ostream &operator<<(std::ostream &os, DayTimeIntervalType::DayMilliseconds interval);

    /// \brief Represents a number of months, days and nanoseconds between
    /// two dates.
    ///
    /// All fields are independent from one another.
    class TURBO_EXPORT MonthDayNanoIntervalType : public IntervalType {
    public:
        struct MonthDayNanos {
            int32_t months;
            int32_t days;
            int64_t nanoseconds;

            bool operator==(MonthDayNanos other) const {
                return this->months == other.months && this->days == other.days &&
                       this->nanoseconds == other.nanoseconds;
            }

            bool operator!=(MonthDayNanos other) const { return !(*this == other); }
        };

        using c_type = MonthDayNanos;
        using PhysicalType = MonthDayNanoIntervalType;

        static_assert(sizeof(MonthDayNanos) == 16,
                      "MonthDayNanos struct assumed to be of size 16 bytes");
        static constexpr Type::type type_id = Type::INTERVAL_MONTH_DAY_NANO;

        static constexpr const char *type_name() { return "month_day_nano_interval"; }

        IntervalType::type interval_type() const override {
            return IntervalType::MONTH_DAY_NANO;
        }

        MonthDayNanoIntervalType() : IntervalType(type_id) {}

        int bit_width() const override { return static_cast<int>(sizeof(c_type) * CHAR_BIT); }

        std::string to_string(bool TURBO_ARG_UNUSED(show_metadata) = false) const override {
            return name();
        }

        std::string name() const override { return "month_day_nano_interval"; }
    };

    TURBO_EXPORT
    std::ostream &operator<<(std::ostream &os,
                             MonthDayNanoIntervalType::MonthDayNanos interval);

    /// \brief Represents an elapsed time without any relation to a calendar artifact.
    class TURBO_EXPORT DurationType : public TemporalType, public ParametricType {
    public:
        using Unit = TimeUnit;

        static constexpr Type::type type_id = Type::DURATION;
        using c_type = int64_t;
        using PhysicalType = Int64Type;

        static constexpr const char *type_name() { return "duration"; }

        int bit_width() const override { return static_cast<int>(sizeof(int64_t) * CHAR_BIT); }

        explicit DurationType(TimeUnit::type unit = TimeUnit::MILLI)
                : TemporalType(Type::DURATION), unit_(unit) {}

        std::string to_string(bool show_metadata = false) const override;

        std::string name() const override { return "duration"; }

        TimeUnit::type unit() const { return unit_; }

    protected:
        std::string compute_fingerprint() const override;

    private:
        TimeUnit::type unit_;
    };

}  // namespace nebula
